package org.yzh.web.service.impl;

import com.github.dozermapper.core.Mapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.yzh.protocol.commons.transform.attribute.AlarmADAS;

import com.ling5821.mtime.Mtime;
import io.github.yezhihao.netmc.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.yzh.protocol.commons.transform.Attribute;
import org.yzh.protocol.t808.T0200;
import org.yzh.web.commons.JsonUtils;
import org.yzh.web.mapper.AdasAlarmMapper;
import org.yzh.web.mapper.AlarmMessageMapper;
import org.yzh.web.mapper.LocationMapper;
import org.yzh.web.model.entity.*;
import org.yzh.web.model.vo.*;
import org.yzh.web.service.AlarmCountService;
import org.yzh.web.service.LocationService;
import org.yzh.web.service.SessionService;
import org.yzh.web.service.VehicleTravelService;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class LocationServiceImpl implements LocationService {

    @Autowired
    private LocationMapper locationMapper;

    @Autowired
    private AdasAlarmMapper adasAlarmMapper;

    @Autowired
    private AlarmMessageMapper alarmMessageMapper;

    @Autowired
    private Mapper dozerMapper;

    @Autowired
    private SessionService sessionService;

    @Autowired
    private VehicleTravelService vehicleTravelService;

    @Autowired
    private AlarmCountService alarmCountService;

    @Qualifier("dataSource")
    @Autowired
    private DataSource dataSource;

    private static final String SQL_HEAD =
        "insert ignore into location (device_time,device_id,mobile_no,plate_no,warning_mark,status,latitude,longitude,altitude,speed,direction,map_fence_id,create_time, attributes) values ";
    private static final String SQL = SQL_HEAD + "(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

    @Override
    public List<Location> find(LocationQuery query) {
        return locationMapper.find(query);
    }

    @Override
    public void customBatchInsert(List<T0200> list) {
        //MySQL预编译语句不支持批量写入，改用SQL拼接方式
        //        jdbcBatchInsert(list);
        jdbcSQLInsert(list);
    }

    public void jdbcBatchInsert(List<T0200> list) {
        LocalDateTime now = LocalDateTime.now();
        Session session;
        String mobileNo, deviceId, plateNo;
        int size = list.size();
        T0200 request;

        try (Connection connection = dataSource.getConnection();
            PreparedStatement statement = connection.prepareStatement(SQL)) {
            for (int i = 0; i < size; i++) {
                request = list.get(i);
                int j = 1;

                session = request.getSession();
                mobileNo = request.getHeader().getMobileNo();
                deviceId = mobileNo;
                plateNo = "";
                DeviceInfo device = (DeviceInfo)session.getSubject();
                if (device != null) {
                    deviceId = device.getDeviceId();
                    plateNo = device.getPlateNo();
                }

                statement.setObject(j++, request.getDateTime());
                statement.setString(j++, deviceId);
                statement.setString(j++, mobileNo);
                statement.setString(j++, plateNo);
                statement.setInt(j++, request.getWarningMark());
                statement.setInt(j++, request.getStatus());
                statement.setInt(j++, request.getLatitude());
                statement.setInt(j++, request.getLongitude());
                statement.setInt(j++, request.getAltitude());
                statement.setInt(j++, request.getSpeed());
                statement.setInt(j++, request.getDirection());
                statement.setInt(j++, 0);
                statement.setObject(j, now);

                statement.addBatch();
            }
            statement.executeLargeBatch();
        } catch (Exception e) {
            log.error("批量写入失败", e);
        }
    }

    public void jdbcSQLInsert(List<T0200> list) {
        String now = Mtime.format();
        Session session;
        String mobileNo, deviceId, plateNo;
        int size = list.size();

        StringBuilder builder = new StringBuilder(size * 132 + 174);
        builder.append(SQL_HEAD);

        for (T0200 request : list) {
            session = request.getSession();
            mobileNo = request.getHeader().getMobileNo();
            deviceId = mobileNo;
            plateNo = "";
            DeviceInfo device = (DeviceInfo)session.getSubject();
            if (device != null) {
                deviceId = device.getDeviceId();
                plateNo = device.getPlateNo();
            }

            builder.append('(');
            builder.append('\'').append(Mtime.format(request.getDateTime())).append('\'').append(',');
            builder.append('\'').append(deviceId).append('\'').append(',');
            builder.append('\'').append(mobileNo).append('\'').append(',');
            builder.append('\'').append(plateNo).append('\'').append(',');
            builder.append(request.getWarningMark()).append(',');
            builder.append(request.getStatus()).append(',');
            builder.append(request.getLatitude()).append(',');
            builder.append(request.getLongitude()).append(',');
            builder.append(request.getAltitude()).append(',');
            builder.append(request.getSpeed()).append(',');
            builder.append(request.getDirection()).append(',');
            builder.append('0').append(',');
            builder.append('\'').append(now).append('\'').append(',');
            builder.append('\'').append(JsonUtils.toJson(request.getAttributes())).append('\'');
            builder.append(')');
            builder.append(',');
        }
        String sql = builder.substring(0, builder.length() - 1);

        try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
            int row = statement.executeUpdate(sql);
            if (row < size) {
                log.warn("批量写入存在重复的主键或唯一键,新增:{},忽略:{}", row, size - row);
            }
        } catch (Exception e) {
            log.error(sql);
            log.error("批量写入失败", e);
        }
    }

    @Override
    public int batchInsert(List<T0200> list) {
        LocalDateTime now = LocalDateTime.now();
        List<LocationDO> locationRecords = new ArrayList<>();
        List<AdasAlarmDO> adasAlarmRecords = new ArrayList<>();
        List<AlarmMessageDO> alarmMessageRecords = new ArrayList<>();
        for (T0200 request : list) {
            Session session = request.getSession();
            DeviceInfo device = (DeviceInfo)session.getSubject();
            if (device == null) {
                continue;
            }
            /* 获取基础信息 */
            String mobileNo = request.getHeader().getMobileNo();
            String deviceId = device.getDeviceId();
            String plateNo = device.getPlateNo();

            BaseDeviceInfo deviceInfo = new BaseDeviceInfo(deviceId, mobileNo, plateNo);

            /* 获取位置数据处理结果 */
            LocationDO locationDO = handleLocation(deviceInfo, request, now);
            locationRecords.add(locationDO);

            /* 获取Adas数据处理结果 */
            AdasAlarmDO adasAlarmDO = handleAdasAlarm(deviceInfo, request);
            if (adasAlarmDO != null) {
                adasAlarmRecords.add(adasAlarmDO);
            }
            /* 获取Adas报警消息 结果 */
            AlarmMessageDO alarmMessageDO = handleAlarmMessage(deviceInfo, request);
            if (alarmMessageDO != null) {
                alarmMessageRecords.add(alarmMessageDO);
            }
        }

        try {
            if (adasAlarmRecords.size() > 0) {
                adasAlarmMapper.batchInsert(adasAlarmRecords);
            }

            if (alarmMessageRecords.size() > 0) {
                alarmMessageMapper.batchInsert(alarmMessageRecords);
            }

            if (locationRecords.size() <= 0) {
                return 0;
            }
            return locationMapper.batchInsert(locationRecords);
        } catch (Exception e) {
            log.error("Location batchInsert: ", e);
            return -1;
        }
    }

    @Override
    public List<DeviceLastLocation> findLastLocation(LocationQuery query) {
        return locationMapper.findLastLocation(query).stream().map(item -> {
            item.setRunningState(sessionService.isExistsByDeviceId(item.getDeviceId()) ? 1 : 0);
            return item;
        }).collect(Collectors.toList());
    }

    @Override
    public DeviceLastLocationDetail findDeviceLastLocationDetail(String plateNo) {
        Optional<DeviceLastLocation> first =
            findLastLocation(LocationQuery.builder().plateNo(plateNo).build()).stream().findFirst();
        DeviceLastLocationDetail detail = new DeviceLastLocationDetail();
        if (first.isPresent()) {
            dozerMapper.map(first.get(), detail);
            detail.setRunningState(sessionService.isExistsByDeviceId(detail.getDeviceId()) ? 1 : 0);
            Optional<VehicleTravelDO> vehicleTravelOp =
                vehicleTravelService.findUnique(detail.getDeviceId(), detail.getPlateNo(), LocalDate.now());
            detail.setTravelTime(vehicleTravelOp.isPresent() ? vehicleTravelOp.get().getTravelTime() : 0);
            Optional<AlarmCountDO> alarmCountOp =
                alarmCountService.findUnique(detail.getDeviceId(), detail.getPlateNo(), LocalDate.now());
            detail.setAlarmCount(alarmCountOp.isPresent() ? alarmCountOp.get().getTotalCount() : 0);
        }
        return detail;
    }

    /**
     * 位置数据处理
     *
     * @param deviceInfo 设备基本信息
     * @param request    Gps消息
     * @param now        开始处理的时间
     * @return {@link LocationDO}
     */
    private LocationDO handleLocation(BaseDeviceInfo deviceInfo, T0200 request, LocalDateTime now) {
        /* 处理位置数据 */
        LocationDO locationDO = dozerMapper.map(request, LocationDO.class);
        locationDO.setDeviceId(deviceInfo.getDeviceId());
        locationDO.setMobileNo(deviceInfo.getMobileNo());
        locationDO.setPlateNo(deviceInfo.getPlateNo());
        locationDO.setMapFenceId(0);
        locationDO.setCreateTime(now);
        locationDO.setDeviceTime(request.getDateTime());
        locationDO.setLatitude(request.getLatitude() / (float)Math.pow(10, 6));
        locationDO.setLongitude(request.getLongitude() / (float)Math.pow(10, 6));
        if (request.getAttributes() == null) {
            locationDO.setAttributes(new HashMap<>());
        }
        return locationDO;
    }

    /**
     * Adas数据处理
     *
     * @param request Gps消息
     * @return {@link AdasAlarmDO}
     */
    private AdasAlarmDO handleAdasAlarm(BaseDeviceInfo deviceInfo, T0200 request) {
        Map<Integer, Attribute> attributes = request.getAttributes();
        if (attributes == null) {
            return null;
        }
        /* 处理ADAS消息 */
        AlarmADAS alarmAdas = (AlarmADAS)attributes.get(AlarmADAS.attributeId);
        if (alarmAdas == null || deviceInfo == null) {
            return null;
        }

        AdasAlarmDO adasAlarmDO = dozerMapper.map(alarmAdas, AdasAlarmDO.class);
        adasAlarmDO.setDeviceId(deviceInfo.getDeviceId());
        adasAlarmDO.setMobileNo(deviceInfo.getMobileNo());
        adasAlarmDO.setPlateNo(deviceInfo.getPlateNo());
        adasAlarmDO.setLatitude(alarmAdas.getLatitude() / (float)Math.pow(10, 6));
        adasAlarmDO.setLongitude(alarmAdas.getLongitude() / (float)Math.pow(10, 6));
        return adasAlarmDO;
    }

    private AlarmMessageDO handleAlarmMessage(BaseDeviceInfo deviceInfo, T0200 request) {
        /* 处理附加报警信息的开始停止 */
        Map<Integer, Attribute> attributes = request.getAttributes();
        if (attributes == null) {
            return null;
        }

        Session session = request.getSession();
        /* 处理ADAS消息 */
        AlarmADAS alarmAdas = (AlarmADAS)attributes.get(AlarmADAS.attributeId);
        if (alarmAdas == null || deviceInfo == null || StringUtils.isEmpty(deviceInfo.getDeviceId())) {
            return null;
        }
        /* 生成报警消息开始标志KEY */
        String key =
            deviceInfo.getDeviceId() + ":" + deviceInfo.getPlateNo() + AlarmMessage.SESSION_KEY + ":" + alarmAdas
                .getType();
        /* 车道偏移需要拼接车道偏移类型 */
        if (alarmAdas.getType() == 0x02) {
            key += ":" + alarmAdas.getDeviateType();
        }
        /* 开始消息处理 */
        if (alarmAdas.getState() == 0x00) {
            AlarmMessageDO alarmMessageDO = new AlarmMessageDO();
            /* 使用终止时间作为消息产生时间 */
            alarmMessageDO.setDateTime(alarmAdas.getDateTime());
            int type = alarmAdas.getType();
            if (type == 0x02) {
                type = alarmAdas.getDeviateType() == 0x01 ? 0x09 : 0x0a;
            }
            alarmMessageDO.setType(type);
            alarmMessageDO.setDuration(0);
            alarmMessageDO.setStartLevel(alarmAdas.getLevel());
            alarmMessageDO.setEndLevel(alarmAdas.getLevel());
            /* 开始速度 */
            alarmMessageDO.setStartSpeed(alarmAdas.getSpeed());
            /* 结束速度 */
            alarmMessageDO.setEndSpeed(alarmAdas.getSpeed());
            alarmMessageDO.setStartLatitude(alarmAdas.getLatitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setStartLongitude(alarmAdas.getLongitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setEndLatitude(alarmAdas.getLatitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setEndLongitude(alarmAdas.getLongitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setDeviceId(deviceInfo.getDeviceId());
            alarmMessageDO.setPlateNo(deviceInfo.getPlateNo());
            alarmMessageDO.setMobileNo(deviceInfo.getPlateNo());
            return alarmMessageDO;
        } else if (alarmAdas.getState() == 0x01) {
            session.setAttribute(key, alarmAdas);
        } else if (alarmAdas.getState() == 0x02) {
            /* 停止消息处理 */
            AlarmADAS startAlarmAdas = (AlarmADAS)session.getAttribute(key);
            if (startAlarmAdas == null) {
                return null;
            }
            AlarmMessageDO alarmMessageDO = new AlarmMessageDO();
            /* 使用终止时间作为消息产生时间 */
            alarmMessageDO.setDateTime(alarmAdas.getDateTime());
            int type = alarmAdas.getType();
            if (type == 0x02) {
                type = alarmAdas.getDeviateType() == 0x01 ? 0x09 : 0x0a;
            }
            alarmMessageDO.setType(type);
            alarmMessageDO
                .setDuration((int)Mtime.getDiffSeconds(startAlarmAdas.getDateTime(), alarmAdas.getDateTime()));
            alarmMessageDO.setStartLevel(startAlarmAdas.getLevel());
            alarmMessageDO.setEndLevel(alarmAdas.getLevel());
            /* 开始速度 */
            alarmMessageDO.setStartSpeed(startAlarmAdas.getSpeed());
            /* 结束速度 */
            alarmMessageDO.setEndSpeed(alarmAdas.getSpeed());
            alarmMessageDO.setStartLatitude(startAlarmAdas.getLatitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setStartLongitude(startAlarmAdas.getLongitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setEndLatitude(alarmAdas.getLatitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setEndLongitude(alarmAdas.getLongitude() / (float)Math.pow(10, 6));
            alarmMessageDO.setDeviceId(deviceInfo.getDeviceId());
            alarmMessageDO.setPlateNo(deviceInfo.getPlateNo());
            alarmMessageDO.setMobileNo(deviceInfo.getPlateNo());
            return alarmMessageDO;
        }
        return null;
    }
}